package com.clx.weblog.module.jwt.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
/**
 * JWT令牌生成和解析功能
 * JWT令牌的生成
 * JWT令牌的解析
 * 秘钥的管理和处理
 * */
// 表示这个类是Spring组件
@Component
public class JwtTokenHelper implements InitializingBean {

    /**
     * 签发人
     */
    @Value("${jwt.issuer}")
    private String issuer;
    /**
     * 秘钥
     */
    private Key key;

    /**
     * JWT 解析
     */
    private JwtParser jwtParser;

    /**
     * 解码配置文件中配置的 Base 64 编码 key 为秘钥
     * @param base64Key
     */
    @Value("${jwt.secret}")
    public void setBase64Key(String base64Key) {
        key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(base64Key));
    }

    /**
     * 初始化 JwtParser
     * @throws Exception
     * Jwts.parserBuilder()：构建一个解析器。
     * requireIssuer(issuer)：指定解析时必须匹配的 "签发者"（Issuer）。
     * setSigningKey(key)：设置用来验证签名的密钥。
     * setAllowedClockSkewSeconds(10)：允许的最大时钟误差为 10 秒
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 考虑到不同服务器之间可能存在时钟偏移，setAllowedClockSkewSeconds 用于设置能够容忍的最大的时钟误差
        jwtParser = Jwts.parserBuilder().requireIssuer(issuer)
                .setSigningKey(key).setAllowedClockSkewSeconds(10)
                .build();
    }

    /**
     * 生成 Token
     * JWT令牌的生成
     * @param username
     * generateToken 方法用于生成一个 JWT 令牌。
     * setSubject(username)：设置 subject，即通常表示用户名或用户标识。
     * setIssuer(issuer)：设置 issuer，即令牌的签发者，通常是你服务器的标识。
     * setIssuedAt：设置 iat，即令牌的签发时间。
     * setExpiration：设置 exp，即令牌的过期时间。在这个例子中，令牌会在一个小时后过期。
     * signWith(key)：使用之前设置的密钥对 JWT 进行签名。
     * compact()：生成最终的 JWT 字符串。
     * @return
     */
    public String generateToken(String username) {
        LocalDateTime now = LocalDateTime.now();
        // Token 一个小时后失效
        LocalDateTime expireTime = now.plusHours(1);

        return Jwts.builder().setSubject(username)
                .setIssuer(issuer)
                .setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
                .setExpiration(Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()))
                .signWith(key)
                .compact();
    }

    /**
     * 解析 Token
     * @param token
     *      * parseToken 方法用于解析传入的 JWT 令牌并验证其有效性。
     *      * jwtParser.parseClaimsJws(token)：通过 jwtParser 解析令牌，并返回包含 JWT 数据的 Jws<Claims> 对象。
     *      * 如果 JWT 无效（比如签名不匹配、格式错误等），会抛出不同的异常：
     *      * SignatureException：签名验证失败。
     *      * MalformedJwtException：JWT 格式不正确。
     *      * UnsupportedJwtException：JWT 类型不支持。
     *      * IllegalArgumentException：参数不合法。
     *      * ExpiredJwtException：JWT 已过期。
     * @return
     */
    public Jws<Claims> parseToken(String token) {
        try {
            return jwtParser.parseClaimsJws(token);
        } catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            throw new BadCredentialsException("Token 不可用", e);
        } catch (ExpiredJwtException e) {
            throw new CredentialsExpiredException("Token 失效", e);
        }
    }

    /**
     * 生成一个 Base64 的安全秘钥
     * @return
     */
    private static String generateBase64Key() {
        // 生成安全秘钥
        Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);

        // 将密钥进行 Base64 编码
        String base64Key = Base64.getEncoder().encodeToString(secretKey.getEncoded());

        return base64Key;
    }

    public static void main(String[] args) {

    }

    /**
     * 校验 Token 是否可用
     * @param token
     * @return
     */
    public void validateToken(String token) {
        jwtParser.parseClaimsJws(token);
    }

    /**
     * 解析 Token 获取用户名
     * @param token
     * @return
     */
    public String getUsernameByToken(String token) {
        try {
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            String username = claims.getSubject();
            return username;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Token 失效时间（分钟）
     */
    @Value("${jwt.tokenExpireTime}")
    private Long tokenExpireTime;
    public String generateToken1(String username) {
        LocalDateTime now = LocalDateTime.now();
        // 设置 Token 失效时间
        LocalDateTime expireTime = now.plusMinutes(tokenExpireTime);

        return Jwts.builder()
                .setSubject(username)
                .setIssuer(issuer)
                .setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
                .setExpiration(Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()))
                .signWith(key)
                .compact();
    }
}
